package com.lau.videoeffect.mediaplayer

import android.content.Context
import android.graphics.SurfaceTexture
import android.media.MediaPlayer
import android.net.Uri
import android.opengl.GLES11Ext
import android.opengl.GLES20
import android.opengl.GLSurfaceView
import android.opengl.Matrix
import android.util.Log
import android.view.Surface
import com.lau.videoeffect.utils.GLTexture
import com.lau.videoeffect.utils.Program
import java.lang.Exception
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10

class GLVideoRenderer : GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener, MediaPlayer.OnVideoSizeChangedListener {
//    https://www.jianshu.com/p/13320a8549db

    companion object {

        private const val TAG = "GLVideoRenderer"

        const val VERTEX_SHADER = "attribute vec4 aPosition;\n" +
                "attribute vec4 aTexCoord;\n" +
                "varying vec2 vTexCoord;\n" +
                "uniform mat4 uMatrix;\n" +
                "uniform mat4 uSTMatrix;\n" +
                "void main() {\n" +
                "    vTexCoord = (uSTMatrix * aTexCoord).xy;\n" +
                "    gl_Position = uMatrix*aPosition;\n" +
                "}"
        const val FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" +
                "precision mediump float;\n" +
                "varying vec2 vTexCoord;\n" +
                "uniform samplerExternalOES sTexture;\n" +
                "void main() {\n" +
                "    gl_FragColor=texture2D(sTexture, vTexCoord);\n" +
                "}"
    }

    var aPosLocation = 0
    var mProgram: Program? = null
    var vertexBuffer: FloatBuffer
    val vertexData = floatArrayOf(
        1f, -1f, 0f,
        -1f, -1f, 0f,
        1f, 1f, 0f,
        -1f, 1f, 0f
    )

    val projectionMatrix = FloatArray(16)
    var uMatrixLocation = 0

    val textureVertexData = floatArrayOf(
        1f, 0f,
        0f, 0f,
        1f, 1f,
        0f, 1f
    )

    var textureVertexBuffer: FloatBuffer
    var uTextureSamplerLocation = 0
    var aTextureCoordLocation = 0
    var mTextureId = 0

    var mSurfaceTexture: SurfaceTexture? = null
    var mMediaPlayer: MediaPlayer
    val mSTMatrix = FloatArray(16)
    var uSTMatrixHandle = 0

    var mPlayerPrepared = false
    var mUpdateSurface = false
    var screenWidth = 0
    var screenHeight = 0

    var mDrawFrameCallback: DrawFrameCallback? = null

    constructor(context: Context, videoPath: String) {
        synchronized(this) {
            mUpdateSurface = false
        }
        vertexBuffer = ByteBuffer.allocateDirect(vertexData.size * 4)
            .order(ByteOrder.nativeOrder())
            .asFloatBuffer()
            .put(vertexData)
        vertexBuffer.position(0)

        textureVertexBuffer = ByteBuffer.allocateDirect(textureVertexData.size * 4)
            .order(ByteOrder.nativeOrder())
            .asFloatBuffer()
            .put(textureVertexData)
        textureVertexBuffer.position(0)

        mMediaPlayer = MediaPlayer()
        mMediaPlayer.setDataSource(context, Uri.parse(videoPath))
        mMediaPlayer.isLooping = true
        mMediaPlayer.setOnVideoSizeChangedListener(this)

    }

    override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {
        mProgram = Program(VERTEX_SHADER, FRAGMENT_SHADER)
        mProgram?.let {
            aPosLocation = it.getAttribLocation("aPosition")
            uMatrixLocation = it.getUniformLocation("uMatrix")
            uSTMatrixHandle = it.getUniformLocation("uSTMatrix")
            uTextureSamplerLocation = it.getUniformLocation("sTexture")
            aTextureCoordLocation = it.getAttribLocation("aTexCoord")
        }
        val textures = IntArray(1)
        GLES20.glGenTextures(1, textures, 0)

        mTextureId = textures[0]
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId)
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST.toFloat())
        mSurfaceTexture = SurfaceTexture(mTextureId)
        mSurfaceTexture!!.setOnFrameAvailableListener(this)

        val surface = Surface(mSurfaceTexture)
        mMediaPlayer.setSurface(surface)
        surface.release()
    }

    override fun onSurfaceChanged(p0: GL10?, p1: Int, p2: Int) {
        screenWidth = p1
        screenHeight = p2

        try {
            mMediaPlayer.prepare()
        } catch (e: Exception) {
            e.printStackTrace()
        }
        mMediaPlayer.start()
        mPlayerPrepared = true
    }

    override fun onDrawFrame(p0: GL10?) {
        GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT xor GLES20.GL_COLOR_BUFFER_BIT)
        synchronized(this) {
            Log.d(TAG, "mUpdateSurface=$mUpdateSurface")
            if (mUpdateSurface) {
                mSurfaceTexture?.getTransformMatrix(mSTMatrix)
                mUpdateSurface = false
            }
        }

        mSurfaceTexture?.updateTexImage()
        mProgram?.use()
        GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0)
        GLES20.glUniformMatrix4fv(uSTMatrixHandle, 1, false, mSTMatrix, 0)

        vertexBuffer.position(0)
        GLES20.glEnableVertexAttribArray(aPosLocation)
        GLES20.glVertexAttribPointer(aPosLocation, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer)

        textureVertexBuffer.position(0)
        GLES20.glEnableVertexAttribArray(aTextureCoordLocation)
        GLES20.glVertexAttribPointer(aTextureCoordLocation, 2, GLES20.GL_FLOAT, false, 8, textureVertexBuffer)

        GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId)

        GLES20.glUniform1i(uTextureSamplerLocation, 0)
        GLES20.glViewport(0, 0, screenWidth, screenHeight)
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
    }

    override fun onFrameAvailable(p0: SurfaceTexture?) {
        mUpdateSurface = true
        Log.d(TAG, "onFrameAvailable mUpdateSurface=$mUpdateSurface")
    }

    override fun onVideoSizeChanged(p0: MediaPlayer?, p1: Int, p2: Int) {
        Log.d(TAG, "onVideoSizeChanged width=$p1, height=$p2")
        updateProjection(p1, p2)
    }

    private fun updateProjection(videoWidth: Int, videoHeight: Int) {
        val screenRatio = screenWidth.toFloat() / screenHeight.toFloat()
        val videoRatio = videoWidth.toFloat() / videoHeight.toFloat()
        if (videoRatio > screenRatio) {
            Matrix.orthoM(projectionMatrix, 0, -1f, 1f, -videoRatio / screenRatio, videoRatio / screenRatio, -1f, 1f)
        } else {
            Matrix.orthoM(projectionMatrix, 0, -screenRatio / videoRatio, screenRatio / videoRatio, -1f, 1f, -1f, 1f)
        }
    }

    fun onResume() {
        if (mPlayerPrepared) {
            mMediaPlayer?.start()
        }
    }

    fun onPause() {
        mMediaPlayer?.pause()
    }

    fun onRelease() {
        mMediaPlayer?.stop()
        mMediaPlayer?.release()
    }

    interface DrawFrameCallback {
        fun onDrawFrame(textureIn: GLTexture, textureOut: GLTexture)
    }

}